﻿using System.Collections.Generic;
#if LOG
using System.Diagnostics;
#endif
using System.Linq;
using Common;
using System;

namespace TeamIce
{
	public class TeamIce : ITeam
    {
		//Игроки с углами относительно мяча и позицией относительно мяча
    	public readonly List<PlayerEx> MyPlayersEx = new List<PlayerEx>(5);
		public readonly List<PlayerEx> EnemyPlayersEx = new List<PlayerEx>(6);
		
		readonly List<PlayerEx> myReflectPlayersEx = new List<PlayerEx>(5);
		readonly List<PlayerEx> myPlayersExPassWithoutOneEnemy = new List<PlayerEx>(10);
        
		PlayerEx myBallOwnerEx;
		PlayerEx myBallOwnerExShootWithoutOneEnemy;
    	private bool playersExFilled;
		private bool reflectPlayersExFilled;
#if LOG
		private static int stepNumber;
		private Team lastOwner;
		//Нужно ли на следующем ходу залогировать скорость мяча, чтобы узнать куда на самом деле он полетел
		private bool logBallVel; 
#endif

        private PlayerType passGiverType;
    	private int passGiveTimer;

        public TeamIce()
        {
#if LOG
        	lastOwner = null;
#endif
            int goalHalfHeight = (int) Field.EnemyGoal.Height/2 - 10;
            breakthroughPointY = new Random().Next(
                (int) Field.EnemyGoal.Position.Y - goalHalfHeight,
                (int) Field.EnemyGoal.Position.Y + goalHalfHeight);
        }

		private readonly bool isRedTeam;
		public TeamIce(bool isRedTeam)
			: this()
		{
			this.isRedTeam = isRedTeam;
		}

        

        
        private static Vector PointOnLineGoalLine(float x, Vector ballPos)
        {
            Line line = new Line(Field.MyGoal.Position, ballPos);
            if (Math.Abs(line.B) < IceConst.EPS)
                return new Vector(x, Field.MyGoal.Position.Y);
            float y = (float) ((-line.C - line.A*x)/(line.B));
            if (y < Field.MyGoal.Top.Y) y = Field.MyGoal.Top.Y;
            if (y > Field.MyGoal.Bottom.Y) y = Field.MyGoal.Bottom.Y;
            return new Vector(x, y);
        }

		private static void KeeperDefend(Ball ball, Player player, Catcher enemyCatcher)
        {
            if ((ball.Owner == null) && (ball.Velocity.LengthSquared > IceConst.EPS))
            {
                if (ball.Velocity.X > 0)
                {
                    player.ActionGo(PointOnLineGoalLine(Math.Min(ball.Position.X, 100), ball.Position));
                }
                else
                {
                    //Ищем пересечение мяча с линией ворот
                    Vector reflectVel;
                    Vector point = IceMath.GetFirstBallReflectPoint(ball.Position, ball.Velocity, out reflectVel);
                    while (reflectVel.X < 0)
                        point = IceMath.GetFirstBallReflectPoint(point, reflectVel, out reflectVel);
                    point.X += Constants.BallMaxPickUpDistance;
                    if (point.Y < Field.MyGoal.Top.Y || point.Y > Field.MyGoal.Bottom.Y)
                        point = PointOnLineGoalLine(player.Position.X, ball.Position);
                    player.ActionGo(point);
                }
            }
            else
            {
                if (player.CanTackle(ball.Owner))
                {
                    player.ActionTackle(ball.Owner);
                    return;
                }
                //player.ActionGo(PointOnLineGoalLine(Math.Min(ball.Position.X, 100), ball.Position));
                //Сокращаем угол обстрела
                Vector ballPos = enemyCatcher.Player == null ? ball.Position : enemyCatcher.CatchPos;
                //Если мы дальше от ворот чем мяч - возвращаемся
                if (player.Position.X > ballPos.X)
                {
                    player.ActionGo(new Vector(0, player.Position.Y));
                    return;
                }

                //Сначала находим угол для вратаря
                PlayerEx keeper = new PlayerEx(player, ballPos, 1);
                //Ищем диапазон ударов по воротам
                Vector posMin = Field.MyGoal.Top - ballPos;
			    Vector posMax = Field.MyGoal.Bottom - ballPos;

                int q1 = IceMath.GetQuarter(posMin);
			    int q2 = IceMath.GetQuarter(posMax);

#if LOG
                Debug.Assert(q1 == 2 || q1 == 3);
                Debug.Assert(q2 == 2 || q2 == 3);
#endif

                double alphaFrom = IceMath.AngleBetween(posMin, new Vector(1, 0));
			    double alphaTo = IceMath.AngleBetween(posMax, new Vector(1, 0));

                if (double.IsNaN(alphaFrom) || double.IsNaN(alphaTo))
                {
                    player.ActionGo(PointOnLineGoalLine(Math.Min(ball.Position.X, 100), ball.Position));
                    return;
                }

                //Если точка находится в 3(-+) или 4(++) четверти, то угол нужно сделать больше Пи
                if (q1 == 3) alphaFrom = Math.PI + Math.PI - alphaFrom;
                if (q2 == 3) alphaTo = Math.PI + Math.PI - alphaTo;

                Sector shoot = new Sector(alphaFrom, alphaTo);
                Sector keep = keeper.Info[IceConst.PlayerMaxShootStr].CatchSector;

				if (double.IsNaN(keep.AlphaFrom) || double.IsNaN(keep.AlphaTo))
				{
					player.ActionGo(PointOnLineGoalLine(Math.Min(ball.Position.X, 100), ball.Position));
					return;
				}

				//приводим углы вратаря к диапазону от 0 до 2Пи
            	while (keep.AlphaFrom < 0)
            	{
            		keep.AlphaFrom += 2*Math.PI;
            		keep.AlphaTo += 2*Math.PI;
            	}

				while (keep.AlphaTo > 2*Math.PI)
				{
					keep.AlphaFrom -= 2*Math.PI;
					keep.AlphaTo -= 2*Math.PI;
				}

#if LOG
            	Debug.Assert(keep.AlphaFrom >= 0 && keep.AlphaFrom <= 2*Math.PI);
            	Debug.Assert(keep.AlphaTo >= 0 && keep.AlphaTo <= 2*Math.PI);
#endif

            	//Если ворота защищены, то не дергаемся
                if (keep.AlphaFrom < shoot.AlphaFrom && keep.AlphaTo > shoot.AlphaTo)
                {
                    player.ActionWait();
                    return;
                }

                Vector dest = player.Position;
                if (ballPos.Y < player.Position.Y)
                {
                    //закрываем верхний угол
                    if (Math.Abs(shoot.AlphaFrom - keep.AlphaFrom) > IceConst.EPS*10)
                        dest.Y = shoot.AlphaFrom > keep.AlphaFrom ? Field.Borders.Height : 0;
                }
                else
                {
                    //закрываем нижний угол
                    if (Math.Abs(shoot.AlphaTo - keep.AlphaTo) > IceConst.EPS * 10)
                        dest.Y = shoot.AlphaTo > keep.AlphaTo ? Field.Borders.Height : 0;
                }

                //Хватает ли радиуса, чтобы перекрыть все ворота на максимальной скорости
                //если не хватает радиуса - нужно увеличивать, приближаясь к мячу
				if (shoot.Size > keep.Size)
					dest.X = IceConst.MaxKeeperWalkDistance;
				else if (shoot.Size < keep.Size + IceConst.EPS * 10)
					dest.X = 0;

                player.ActionGo(dest);
            }
        }

        //Возвращает новую позицию игрока, после следующего шага в указанном направлении
		internal static Vector SimulateMove(IcePlayer player, Vector dest)
        {
            Vector resVel; 
            return SimulateMove(player.Position, player.Velocity, dest, out resVel);
        }

		internal static Vector SimulateMove(Player player, Vector dest)
		{
			Vector resVel;
			return SimulateMove(player.Position, player.Velocity, dest, out resVel);
		}

		//Возвращает новую позицию и скорость игрока, после следующего шага в указанном направлении
		internal static Vector SimulateMove(Vector pos, Vector vel, Vector dest, out Vector newVelocity)
		{
			Vector dir = (dest - pos).Unit();
			newVelocity = (vel + dir * Constants.PlayerMaxVelocity * Constants.PlayerAccelerationFactor) *
							Constants.PlayerSlowDownFactor;
			return pos + newVelocity;
		}

		private static bool ShouldKeeperMakePass(IPosition player, Team myTeam, Team enemyTeam)
		{
			if (player.Position.X > IceConst.MaxKeeperWalkDistance)
				return true;
			Player closestEnemy = player.GetClosest(enemyTeam);
			if (player.GetDistanceTo(closestEnemy) < IceConst.KeeperSafeRadius)
				return true;
			Player closestFriend = player.GetClosest(myTeam);
			return (player.GetDistanceTo(closestFriend) < IceConst.KeeperSafeRadius);
		}

		//Возвращает оценочный коэффициент от 0 до 10 для движения к цели
		//10 - движение безопасно
		//0 - движение невозможно
        private static int GetMoveDangerScore(IcePlayer player, Team myTeam, Team enemyTeam, Vector moveTarget)
        {
			if (player.PlayerType == PlayerType.Keeper)
				if (ShouldKeeperMakePass(player, myTeam, enemyTeam))
					return 0;

			//Иммунитет к подкатам после отбора мяча успешным подкатом
			if (player.FallenTimer < -1)
				return -player.FallenTimer;
			
        	Vector target = SimulateMove(player, moveTarget);
        	int koef = 10;
			foreach (
				Player enemy in 
				from enemy in enemyTeam.Players.Where(e => e.FallenTimer <= 1) 
				let enemyTarget = enemy.FallenTimer == 1 ? enemy.Position : SimulateMove(enemy, target) 
				where Vector.Distance(target, enemyTarget) < Constants.PlayerMaxTackleDistance
				select enemy)
			{
				if (enemy.TackleTimer == 0)
					return 0;
				if (enemy.TackleTimer < 10)
					koef -= (10 - enemy.TackleTimer);
				if (koef < 1)
					koef = 1;
			}
			return koef;
        }

        //Сможем ли догнать и сбить соперника, прежде чем он поймает мяч
		public bool CanTackleEnemyInterceptor(List<Player> myTeam, List<Player> enemyTeam, PlayerEx excludedEnemy, int str, Sector mySector, Vector ballPos, PlayerType excludeType)
		{
			if (!excludedEnemy.Info[str].CatchSector.IsNotNaN)
				return false;

			List<Player> myPlayers = myTeam.Where(
						p =>
						p.PlayerType != PlayerType.Keeper &&
						p.PlayerType != myBallOwnerEx.Player.PlayerType &&
						p.FallenTimer <= 0 &&
						p.PlayerType != excludeType).ToList();

			Player excludedEnemyPlayer = enemyTeam.Find(p => p.PlayerType == excludedEnemy.Player.PlayerType);

			//Пускаем мяч по оптимальной для соперника траектории
			double alpha = PlayerEx.FindOptimalEnemyAlpha(excludedEnemy.Info[str].CatchSector, mySector);
			float shootStrength = IceConst.GetBallVelocityByShootStrength(str);
			Vector dest = IceMath.PointOnAngle(ballPos, alpha);
			Vector ballVel = (dest - ballPos).Unit() * shootStrength;
			//Смотрим где соперник его поймает
		    VectorSteps enemyCatchPos =
		        IceMath.CatchBallWithConstVelocities(ballPos, ballVel*IceConst.AverageBallSlowdown,
		                                             excludedEnemy.Player.Position, IceConst.AveragePlayerSpeed);
			Catcher enemyCatcher = new Catcher(excludedEnemyPlayer, enemyCatchPos.Position, enemyCatchPos.Steps);
			Player enemyCatcherTackler;
			//Если сможем его сбить до того, как он поймает мяч - добавляем
			return CanTackleEnemyCatcher(myPlayers, enemyCatcher, out enemyCatcherTackler);
		}

		//Для своего игрока, который кладеет мячом проверяем возможность удара за вычетом одного из игроков соперника,
		//которого мы попробуем сбить прежде, чем он перехватит мяч
		public void FillBallOwnerShootWithoutOneEnemy(Vector ballPos, List<Player> myTeam, List<Player> enemyTeam)
		{
			if (!playersExFilled) return;
			if (myBallOwnerEx.Player == null)
				return;
			
			IcePlayer myIcePlayer = new IcePlayer(myBallOwnerEx.Player);
			for (int i = 0; i < EnemyPlayersEx.Count; i++)
			{
				PlayerEx newPlayerEx = new PlayerEx(myIcePlayer, ballPos, 1);
				PlayerEx excludedEnemy = EnemyPlayersEx[i];

				List<PlayerEx> enemies = new List<PlayerEx>(6);
			    enemies.AddRange(EnemyPlayersEx.Where(e => e.Player.PlayerType != excludedEnemy.Player.PlayerType));
			    newPlayerEx.FillShootSectors(enemies);
				if (newPlayerEx.BestShootSafeness != Safeness.Safe) continue;

				//Без исключенного соперника наш игрок сможет ударить
				//Проверяем сможем ли мы сбить исключенного игрока
				int str = newPlayerEx.BestShootStrength;
				if (!newPlayerEx.Info[str].ShootSector.IsNotNaN)
					continue;

                if (!CanTackleEnemyInterceptor(myTeam, enemyTeam, excludedEnemy, str, newPlayerEx.Info[str].ShootSector, ballPos, myBallOwnerEx.Player.PlayerType))
					continue;

				myBallOwnerExShootWithoutOneEnemy = newPlayerEx;
#if LOG
                Logger.Log("\nShoot without one enemy: \nstrength {0} without {1}",
                    myBallOwnerExShootWithoutOneEnemy.BestShootStrength, Logger.TypeToStr(excludedEnemy.Player.PlayerType));
#endif
				return;
			}
			
		}

		//Для своих игроков заполняем возможность паса за вычетом одного из игроков соперника,
		//которого мы попробуем сбить прежде, чем он перехватит мяч
		public void FillPlayersExPassWithoutOneEnemy(Vector ballPos, List<Player> myTeam, List<Player> enemyTeam)
        {
			if (!playersExFilled) return;
			foreach (PlayerEx playerEx in MyPlayersEx)
			{
				IcePlayer myIcePlayer = new IcePlayer(playerEx.Player);
				for (int i = 0; i < EnemyPlayersEx.Count; i++)
				{
					PlayerEx newPlayerEx = new PlayerEx(myIcePlayer, ballPos, 1);
					PlayerEx excludedEnemy = EnemyPlayersEx[i];
					
					List<PlayerEx> enemies = new List<PlayerEx>(6);
				    enemies.AddRange(EnemyPlayersEx.Where(e => e.Player.PlayerType != excludedEnemy.Player.PlayerType));
				    newPlayerEx.FillPassSectors(enemies);
					newPlayerEx.FillShootSectors(EnemyPlayersEx);
					newPlayerEx.FillPassScore(ballPos, newPlayerEx.Player.Position, EnemyPlayersEx);
					if (newPlayerEx.BestPassSafeness != Safeness.Safe) continue;
				
					//Без исключенного соперника наш игрок сможет получить пас
					//Проверяем сможем ли мы сбить исключенного игрока
					int str = newPlayerEx.BestPassStrength;
					if (!excludedEnemy.Info[str].CatchSector.IsNotNaN || !newPlayerEx.Info[str].PassSector.IsNotNaN)
						continue;

                    if (!CanTackleEnemyInterceptor(myTeam, enemyTeam, excludedEnemy, str, newPlayerEx.Info[str].PassSector, ballPos, playerEx.Player.PlayerType))
						continue;

					myPlayersExPassWithoutOneEnemy.Add(newPlayerEx);
					break;
				}
			}
        }

		//Для своих игроков заполняется возможность дать пас от стенки
		public void FillPlayersExReflectPass(Vector ballPos, List<Player> myTeam, List<Player> enemyTeam)
        {
			if (reflectPlayersExFilled) return;
			if (!playersExFilled) return;

        	foreach (PlayerEx playerEx in MyPlayersEx)
        	{
        		Vector reflectPoint;
				Border border = IceMath.ClosestReflectPoint(ballPos, playerEx.Player.Position, out reflectPoint);
                //От левой стенки давать пас опасно - часто в свои ворота залетает
                if (border == Border.Left)
                    continue;
				IcePlayer myIcePlayer = IceMath.ReflectPlayer(new IcePlayer(playerEx.Player), border);

				PlayerEx reflectPlayerEx = new PlayerEx(myIcePlayer, ballPos, 1);

				List<PlayerEx> enemies = new List<PlayerEx>(5);
        	    enemies.AddRange(enemyTeam
        	                         .Select(enemy => IceMath.ReflectPlayer(new IcePlayer(enemy), border))
        	                         .Select(enemyIcePlayer => new PlayerEx(enemyIcePlayer, ballPos, 2)));
                enemies.AddRange(EnemyPlayersEx);

        	    reflectPlayerEx.FillPassSectors(enemies);
				//Сектора для удара можно использовать от исходного игрока, 
				//ведь мы пас в итоге все равно даем именно ему
				reflectPlayerEx.CopyShootSectors(playerEx);
				//Для оценки позиции используем реальную позицию игрока (не отраженную),
				//а для оценки дальности паса используем расстояние до отраженного игрока
				reflectPlayerEx.FillPassScore(ballPos, playerEx.Player.Position, enemies,
							PlayerEx.GetPassDistScore(reflectPlayerEx.Player.Position, ballPos));
				myReflectPlayersEx.Add(reflectPlayerEx);
        	}
			
        	reflectPlayersExFilled = true;
        }

    	//Заполняет класс PlayerEx для всех игроков, кроме владельца мяча
		public void FillPlayersEx(Vector ballPos, Player ballOwner, List<Player> myTeam, List<Player> enemyTeam)
        {
			if (playersExFilled)
                return;

			myBallOwnerEx = new PlayerEx(ballOwner, ballPos, 1);

            MyPlayersEx.AddRange(
                from player in myTeam
                where player.PlayerType != ballOwner.PlayerType
                select new PlayerEx(player, ballPos, 1));
            EnemyPlayersEx.AddRange(
                from player in enemyTeam
                select new PlayerEx(player, ballPos, 2));

			foreach (PlayerEx player in MyPlayersEx)
            {
				player.FillPassSectors(EnemyPlayersEx);
				player.FillShootSectors(EnemyPlayersEx);
				player.FillPassScore(ballPos, player.Player.Position, EnemyPlayersEx);
            }
			myBallOwnerEx.FillShootSectors(EnemyPlayersEx);

        	playersExFilled = true;
        }

        private static Catcher FindCatcher(List<Player> players, Ball ball, bool canKeeperBeCatcher = true)
        {
            Catcher catcher = new Catcher(null, Vector.Zero, Int32.MaxValue);

            if (ball.Owner != null)
                return catcher;

            foreach (Player player in players)
            {
                if (!canKeeperBeCatcher && player.PlayerType == PlayerType.Keeper)
                    continue;

                VectorSteps vs =
                    IceMath.CatchBallWithConstVelocities(ball.Position, ball.Velocity*IceConst.AverageBallSlowdown,
                                                         player.Position, IceConst.AveragePlayerSpeed);
                if (player.FallenTimer > 0)
                    vs.Steps += player.FallenTimer;
                if (vs.Steps >= catcher.CatchSteps) continue;
                catcher.CatchPos = vs.Vector;
                catcher.Player = player;
                catcher.CatchSteps = vs.Steps;
            }
            return catcher;
        }

		private static bool ProcessCatcher(Team myTeam, Team enemyTeam, Ball ball, out Catcher myCatcher, out Catcher enemyCatcher)
        {
            myCatcher = FindCatcher(myTeam.Players, ball);
            enemyCatcher = FindCatcher(enemyTeam.Players, ball);

            if (myCatcher.Player == null) return false;
            if (myCatcher.Player.PlayerType == PlayerType.Keeper)
            {
                //Если за мячом бежать вратарю, то проверяем, что до мяча он доберется раньше соперников
                if (myCatcher.CatchSteps >= enemyCatcher.CatchSteps)
                    myCatcher = FindCatcher(myTeam.Players, ball, false);
            }
            myCatcher.ActionGo();
            return myCatcher.CatchSteps < enemyCatcher.CatchSteps;
        }

		private Vector FindPositionForOpen(Player player, Vector ballPos)
		{
			PlayerType type = player.PlayerType;
            Vector pos = ballPos;
			switch (type)
            {
                case PlayerType.CenterForward:
					pos = (ballPos + Field.EnemyGoal.Center + Field.Borders.Center) / 3;
                    break;
                case PlayerType.LeftForward:
					pos = (ballPos + Field.EnemyGoal.Top + new Vector(Field.Borders.Width, 0)) / 3;
                    break;
                case PlayerType.RightForward:
					pos = (ballPos + Field.EnemyGoal.Bottom + new Vector(Field.Borders.Width, Field.Borders.Height)) / 3;
                    break;
                case PlayerType.LeftDefender:
                    pos = ballPos + new Vector(-100, -300);
					if (pos.X > IceConst.MaxDefendersWalkDistance)
						pos.X = IceConst.MaxDefendersWalkDistance;
                    break;
                case PlayerType.RightDefender:
                    pos = ballPos + new Vector(-100, 300);
					if (pos.X > IceConst.MaxDefendersWalkDistance)
						pos.X = IceConst.MaxDefendersWalkDistance;
                    break;
            }

			if (!playersExFilled)
				return IceMath.BoundPoint(pos);
			PlayerEx playerEx = MyPlayersEx.Find(p => p.Player.PlayerType == player.PlayerType);
			if (playerEx == null)
				return IceMath.BoundPoint(pos);
			if (playerEx.SectorForOpen == null)
				return IceMath.BoundPoint(pos);
			if (double.IsNaN(playerEx.SectorForOpen.AlphaFrom) || double.IsNaN(playerEx.SectorForOpen.AlphaTo))
				return IceMath.BoundPoint(pos);

			double alpha = playerEx.SectorForOpen.Average;
			/*double dx = IceConst.BestDistanceForFree * Math.Cos(alpha);
			double dy = IceConst.BestDistanceForFree * Math.Sin(alpha);

			
			Vector point = new Vector(ballPos.X + dx, ballPos.Y - dy);*/

		    int dist = (int)IceConst.BestDistanceForFree;
            Vector point = IceMath.BoundPointOnAngle(ballPos, alpha, (int)(dist * Math.Cos(alpha)));
			if (point.GetDistanceTo(ballPos) < IceConst.EPS)
				return IceMath.BoundPoint(pos);

			if (point.X > IceConst.MaxDefendersWalkDistance &&
				(type == PlayerType.LeftDefender || type == PlayerType.RightDefender))
				return IceMath.BoundPoint(pos);

			return !Field.Borders.Contains(point) ? IceMath.BoundPoint(pos) : point;
		}

		//Выбиваем мяч, стараясь не попасть в соперников
		public double GetKickOutAngle(Vector playerPos, double alphaFrom, double alphaTo)
		{
            Sector freeSector = PlayerEx.BiggestFreeSector(alphaFrom, alphaTo, EnemyPlayersEx,
		                                                   IceConst.PlayerMaxShootStr);

            if (freeSector == null)
                return playerPos.Y > Field.Borders.Center.Y ? alphaFrom : alphaTo;
			return freeSector.Average;
		}

		private Vector GetKickOutTarget(Vector playerPos, double alphaFrom, double alphaTo)
		{
			double alpha = GetKickOutAngle(playerPos, alphaFrom, alphaTo);
			return IceMath.BoundPoint(playerPos + IceMath.PointOnAngle(Vector.Zero, alpha));
		}

		private static bool DetectInLeadTactics(Team enemyTeam)
		{
			return
				!(enemyTeam.Players.Any(player => player.Position.X < Field.Borders.Width - 100));
		}

		#region AntiInLeadTactic
		private readonly float breakthroughPointY;

		private void AntiInLeadTactic(Team myTeam, Team enemyTeam, Ball ball)
        {
            Player striker = myTeam.Players[0];
			if (ball.Owner == null)
			{
                Catcher catcher;
                Catcher enemyCatcher;
				ProcessCatcher(myTeam, enemyTeam, ball, out catcher, out enemyCatcher);
				striker = catcher.Player;
			}
			else if (ball.Owner.Team == myTeam)
			{
				foreach (Player player in myTeam.Players.Where(player => ball.Owner == player))
					striker = player;
			}

            Vector fromPos = new Vector(Field.Borders.Width - 100 - Constants.PlayerMaxTackleDistance, breakthroughPointY);
            Vector targetPos = new Vector(Field.Borders.Width, breakthroughPointY);

        	bool isBreakThrough = (striker.Position.X > fromPos.X) && (ball.Owner == striker);

        	List<Player> enemies = new List<Player>(6);
			if (isBreakThrough)
			{
				Vector nextPos = SimulateMove(striker, targetPos);
				foreach (Player enemy in enemyTeam.Players)
				{
					if (enemy.FallenTimer <= 1 && enemy.TackleTimer <= 1 &&
					    enemy.GetDistanceTo(nextPos) < Constants.PlayerMaxTackleDistance)
						enemies.Add(enemy);
				}
			}

			int i = 0;
        	foreach (Player player in myTeam.Players.Where(player => player != striker))
            {
				if (isBreakThrough)
				{
					bool found = false;
					foreach (Player enemy in enemies)
					{
						if (!player.CanTackle(enemy)) continue;
						player.ActionTackle(enemy);
						enemies.Remove(enemy);
						found = true;
						break;
					}
					if (!found) player.ActionGo(targetPos);
				}
                else
                    player.ActionGo(new Vector(fromPos.X + Constants.PlayerMaxTackleDistance*0.9 * i, breakthroughPointY));
            	i++;
            }

            if (ball.Owner == null)
            {
                if (striker.CanPickUpBall(ball))
                {
                    striker.ActionPickUpBall();
                }
                else
                {
                    VectorSteps vs =
                        IceMath.CatchBallWithConstVelocities(ball.Position, ball.Velocity*IceConst.AverageBallSlowdown,
                                                             striker.Position, IceConst.AveragePlayerSpeed);
                    striker.ActionGo(vs.Vector);
                }
            }
            else if (ball.Owner.Team == myTeam && ball.Owner != striker)
            {
                ball.Owner.ActionShoot(striker);
            }
            else if (ball.Owner.Team == enemyTeam)
            {
                if (striker.CanTackle(ball.Owner)) 
                    striker.ActionTackle(ball.Owner);
                else
                    striker.ActionGo(ball.Owner);
            }
            else
            {
				if (isBreakThrough)
				{
					if (Math.Abs(striker.Position.GetDistanceTo(targetPos)) > 2)
						striker.ActionGo(targetPos);
					else
						striker.ActionShoot(targetPos);
				}
				else if (Math.Abs(striker.Position.GetDistanceTo(fromPos)) > 2)
					striker.ActionGo(fromPos);
				else
					striker.ActionGo(targetPos);
            }
        }
		#endregion

		public static Vector GetDefencePoint(Vector enemyPos, Vector ballPos)
		{
            return IceMath.BoundPoint(enemyPos - (enemyPos - ballPos).Unit() * Constants.PlayerMaxTackleDistance);
		}

		//Есть ли возможность сбить соперника, который должен завладеть мячом
		//Направляет игрока в нужную сторону или делает подкат
		private static bool CanTackleEnemyCatcher(List<Player> myPlayers, Catcher enemyCatcher, out Player enemyCatcherTackler)
		{
			//Если можем сделать подкат прямо сейчас делаем его
			foreach (Player player in myPlayers.Where(myPlayer => myPlayer.CanTackle(enemyCatcher.Player)))
			{
				player.ActionTackle(enemyCatcher.Player);
				enemyCatcherTackler = player;
				return true;
			}

			//Если игрок уже на линии перехвата, то пусть двигается навстречу сопернику
			foreach (Player player in myPlayers
					.Where(player => 
							player.TackleTimer < enemyCatcher.CatchSteps &&
							Line.IsPointOnSector(player.Position, enemyCatcher.CatchPos, enemyCatcher.Player.Position, Constants.PlayerMaxTackleDistance / 2)))
			{
				player.ActionGo(enemyCatcher.Player);
				if (player.TackleTimer > 0)
				{
					//Проверяем успеет ли закончиться таймер, если идти навстречу сопернику
					float dist = Vector.Distance(player, enemyCatcher.Player);
					int steps = (int)(dist/(IceConst.AveragePlayerSpeed*2));
					if (player.TackleTimer > steps)
						player.ActionGo(enemyCatcher.CatchPos);
				}
				enemyCatcherTackler = player;
				return true;
			}

			//Ищем игрока, ближайшего к линии перехвата
			foreach (Player player in myPlayers.Where(p => p.TackleTimer < enemyCatcher.CatchSteps))
			{
				Vector movePoint;
				double dist = Line.DistanceToLineSector(player.Position, enemyCatcher.CatchPos, enemyCatcher.Player.Position, out movePoint);
				if (dist >= enemyCatcher.Player.GetDistanceTo(movePoint)) continue;
				//Если до точки перехвата мы доберемся раньше
				enemyCatcherTackler = player;
				enemyCatcherTackler.ActionGo(movePoint);
				return true;
			}

			enemyCatcherTackler = null;
			return false;
		}

		//когда мячом владеет соперник, посылает игроков на линию перехвата мяча
		//после выхода на линию перехвата игрок начинает двигаться по ней в сторону соперника
        private static void MoveToDefencePositions(Team myTeam, Team enemyTeam, Ball ball, 
			Catcher catcher, Catcher enemyCatcher, List<Player> tacklers = null)
        {
            if (ball.Owner != null && ball.Owner.Team == myTeam)
                return;

            List<Player> myPlayers = myTeam.Players
                .Where(p => 
					p.PlayerType != PlayerType.Keeper && 
					(catcher.Player == null || p.PlayerType != catcher.Player.PlayerType) &&
					p.FallenTimer <= 0 && 
					(tacklers == null || !tacklers.Contains(p)))
                .ToList();
        	List<Player> enemies = enemyTeam.Players.Where(e => e != ball.Owner).ToList();
			PlayerComparer comparer = new PlayerComparer();

			bool ballWillBeEnemy = ball.Owner != null || (enemyCatcher.CatchSteps <= catcher.CatchSteps);
			//Если мячем завладеет соперник проверяем есть ли шанс его сбить
			if (ball.Owner == null && ballWillBeEnemy)
			{
				Player enemyCatcherTackler;
				if (CanTackleEnemyCatcher(myPlayers, enemyCatcher, out enemyCatcherTackler))
				{
#if LOG
					Logger.Log("Try to tackle enemy catcher. Step: {0}", stepNumber);
#endif
					myPlayers.Remove(enemyCatcherTackler);
					ballWillBeEnemy = false;
				}
            }
            
 			//Если мяч уже у соперника или будет у него
			if (ballWillBeEnemy)
			{
				Vector ballPos = ball.Owner != null ? ball.Position : enemyCatcher.CatchPos;

				Player closestPlayer = null;
				Vector defencePoint = ballPos;
				double min = Double.MaxValue;

				//ищем ближайшего среди игроков, которые уже находятся на отрезке мяч-ворота
				foreach (Player player in myPlayers
					.Where(player => Line.IsPointOnSector(player.Position, Field.MyGoal.Position, ballPos, Constants.PlayerMaxTackleDistance / 2)))
				{
					double dist = player.GetDistanceTo(defencePoint);
					if (dist >= min) continue;
					min = dist;
					closestPlayer = player;
				}

				//если не нашли, то ищем ближайшего к линии мяч-ворота со стороны ворот
				if (closestPlayer == null)
				{
					min = Double.MaxValue;
					foreach (Player player in myPlayers.Where(p => p.Position.X < ballPos.X + Constants.PlayerMaxTackleDistance / 2))
					{
						Vector point;
						double dist = Line.DistanceToLineSector(player.Position, Field.MyGoal.Position, ballPos, out point);
						if (dist >= min) continue;
						min = dist;
						closestPlayer = player;
						defencePoint = point;
					}
				}

				//если не нашли, то ищем ближайшего к линии мяч-ворота
				if (closestPlayer == null)
				{
					min = Double.MaxValue;
					foreach (Player player in myPlayers)
					{
						Vector point;
						double dist = Line.DistanceToLineSector(player.Position, Field.MyGoal.Position, ballPos, out point);
						if (dist >= min) continue;
						min = dist;
						closestPlayer = player;
						defencePoint = point;
					}
				}

				if (closestPlayer != null)
				{
				    Player ballOwner = enemyTeam.Players.Find(p => p == ball.Owner);
				    Player myPlayer = myTeam.Players.Find(player => player == closestPlayer);
                    if (myPlayer.CanTackle(ballOwner))
                        myPlayer.ActionTackle(ballOwner);
                    else
                        myPlayer.ActionGo(IceMath.BoundPoint(defencePoint));
					myPlayers.Remove(closestPlayer);
				}
            }

            enemies.Sort(comparer);

			foreach (Player e in enemies
				.Where(e => e.Position.X < IceConst.DefenceLine))
        	{
                Player enemy = e;

				Player closestPlayer = null;
				Vector defencePoint = GetDefencePoint(enemy.Position, ball.Position);
				double min = Double.MaxValue;

                //ищем ближайшего среди игроков, которые уже находятся на отрезке мяч-соперник
        	    foreach (Player player in myPlayers
                    .Where(player => Line.IsPointOnSector(player.Position, enemy.Position, ball.Position, Constants.PlayerMaxTackleDistance / 2)))
				{
					double dist = player.GetDistanceTo(defencePoint);
					if (dist >= min) continue;
					min = dist;
					closestPlayer = player;
				}

				//если не нашли, то ищем ближайшего к линии мяч-соперник
				if (closestPlayer == null)
				{
					min = Double.MaxValue;
					foreach (Player player in myPlayers)
					{
						Vector point;
						double dist = Line.DistanceToLineSector(player.Position, enemy.Position, ball.Position, out point);
						if (dist >= min) continue;
						min = dist;
						closestPlayer = player;
						defencePoint = point;
					}
				}
				
				if (closestPlayer == null) continue;
                Player myPlayer = myTeam.Players.Find(player => player == closestPlayer);
                if (myPlayer.CanTackle(enemy))
                    myPlayer.ActionTackle(enemy);
                else
                    myPlayer.ActionGo(IceMath.BoundPoint(defencePoint));
				myPlayers.Remove(closestPlayer);
        	}
        }

		private static Vector GetBallOwnerMoveTarget(IPosition player)
		{
			return player.Position.X > (2/3)*Field.Borders.Width
			       	? Field.EnemyGoal.Position
			       	: new Vector(Field.EnemyGoal.Position.X, player.Position.Y);
		}

		private void PlayerWithBallDecision(Player player, Team myTeam, Team enemyTeam, MatchInfo matchInfo)
        {
			List<Move> moves = GenerateMoves(myTeam, enemyTeam);
			if (moves.Count == 0)
				return;
			moves.Sort(Move.CompareByScoreDesc);
        	Move bestMove = moves[0];

			if (bestMove.Type == MoveType.Pass || bestMove.Type == MoveType.Shoot)
			{
				passGiverType = player.PlayerType;
				passGiveTimer = IceConst.PassCoolDownSteps;

#if LOG
				Logger.Log("");
				Logger.Log("step: {4} {3} target: {0:0.00} {1:0.00} score: {2}", bestMove.Target.Position.X, bestMove.Target.Y, bestMove.Score,
					        bestMove.Type == MoveType.Pass ? "pass" : "shoot", matchInfo.CurrentTimeStep);
				if (bestMove.Type == MoveType.Pass)
				{
					PlayerEx passTo = bestMove.PassTo;
					Logger.Log("pass to: {2} {0:0.00} {1:0.00}", passTo.Player.Position.X, passTo.Player.Position.Y,
						        Logger.TypeToStr(passTo.Player.PlayerType));
					Logger.Log("pass angle: {0:0.00}", passTo.Info[bestMove.Strength].PassSector.Average);
					Logger.Log("Strength {2} CatchSector: {0} PassSector: {1}",
									passTo.Info[bestMove.Strength].CatchSector.ToString(),
									passTo.Info[bestMove.Strength].PassSector.ToString(), bestMove.Strength);
				}

				Logger.LogPositions(player.Position, myTeam, enemyTeam);
				logBallVel = true;
#endif
			}

			moves[0].Make(player);
        }

		private List<Move> GenerateMoves(Team myTeam, Team enemyTeam)
		{
			Vector ballOwnerPos = myBallOwnerEx.Player.Position;

			List<Move> moves = new List<Move>(10);

			//удар
			if (myBallOwnerEx.ShootScore > 0)
			{
				moves.Add(new Move(MoveType.Shoot, myBallOwnerEx.BestShootTarget,
				                   myBallOwnerEx.BestShootStrength, int.MaxValue));
				return moves;
			}
			
			//пасы
		    moves.AddRange(
                MyPlayersEx
                .Where(p => p.BestPassSafeness == Safeness.Safe)
                .Select(playerEx => 
					new Move(playerEx, playerEx.BestPassTarget, playerEx.BestPassStrength, playerEx.PassScore)));
            FillPlayersExReflectPass(ballOwnerPos, myTeam.Players, enemyTeam.Players);
            moves.AddRange(
                myReflectPlayersEx
                    .Where(p => p.BestPassSafeness == Safeness.Safe)
                    .Select(playerEx =>
                            new Move(playerEx, playerEx.BestPassTarget, playerEx.BestPassStrength, (int)(playerEx.PassScore*0.8))));
			
		    //движение
			Vector moveTarget = GetBallOwnerMoveTarget(myBallOwnerEx.Player);
			int moveDangerScore = GetMoveDangerScore(myBallOwnerEx.Player, myTeam, enemyTeam, moveTarget);
			if (moveDangerScore > 0)
			{
				int moveScore = myBallOwnerEx.GetSummaryScore(moveTarget, ballOwnerPos, EnemyPlayersEx, moveDangerScore);
				moves.Add(new Move(MoveType.Move, moveTarget, 0, moveScore));
			}
			else if (myBallOwnerEx.Player.PlayerType == PlayerType.Keeper || ballOwnerPos.X < IceConst.MaxKeeperWalkDistance)
			{
				//вынос мяча голкипером или вблизи своих ворот
				Vector kickOutTarget = GetKickOutTarget(ballOwnerPos, -Math.PI / 4, Math.PI / 4);
				moves.Add(new Move(MoveType.Shoot, kickOutTarget, IceConst.PlayerMaxShootStr, 0));
			}
			else if (moves.Count == 0)
            {
                //Удар без одного из соперников, которого мы попробуем сбить
                FillBallOwnerShootWithoutOneEnemy(ballOwnerPos, myTeam.Players, enemyTeam.Players);
                if (myBallOwnerExShootWithoutOneEnemy != null && myBallOwnerExShootWithoutOneEnemy.ShootScore > 0)
                {
                    moves.Add(new Move(MoveType.Shoot, myBallOwnerExShootWithoutOneEnemy.BestShootTarget,
                                       myBallOwnerExShootWithoutOneEnemy.BestShootStrength, int.MaxValue / 2));
                    return moves;
                }


				//Добавляем пасы, которые смогут пройти если сбить одного из соперников
				FillPlayersExPassWithoutOneEnemy(ballOwnerPos, myTeam.Players, enemyTeam.Players);
				moves.AddRange(
					myPlayersExPassWithoutOneEnemy
						.Where(p => p.BestPassSafeness == Safeness.Safe)
						.Select(playerEx =>
								new Move(playerEx, playerEx.BestPassTarget, playerEx.BestPassStrength, playerEx.PassScore/2)));
#if LOG
				if (moves.Count > 0)
					Logger.Log("\nPASS WITHOUT 1 ENEMY step: {0}:", stepNumber);
#endif

				if (moves.Count == 0)
				{
					//Добавляем вынос мяча
					Vector kickOutTarget = GetKickOutTarget(ballOwnerPos, -Math.PI/4, Math.PI/4);
					moves.Add(new Move(MoveType.Shoot, kickOutTarget, IceConst.PlayerMaxShootStr, 0));
#if LOG
					if (moves.Count > 0)
						Logger.Log("\nKICK OUT (moveDanger: {0}) step: {1}:", moveDangerScore, stepNumber);
#endif
				}
			}
			return moves;
		}

        private static int StepsToTackle(Ball ball, Player enemy, Player player, out Vector target)
        {
            target = Vector.Zero;
            if (player.CanTackle(enemy))
                return 0;
            if (player.FallenTimer > ball.PickUpTimer)
                return int.MaxValue;
            if (player.TackleTimer > ball.PickUpTimer)
                return int.MaxValue;
            for (int i = 1; i < ball.PickUpTimer; i++)
            {
                Vector newBallPos = IceMath.CalcBallPos(ball.Position, ball.Velocity, i);
                Vector newEnemyPos = enemy.Position;
                Vector newEnemyVel = enemy.Velocity;
                for (int j = 1; j <= i; j++)
                {
                    newEnemyPos = SimulateMove(newEnemyPos, newEnemyVel, newBallPos, out newEnemyVel);
                    if (j == 1)
                        target = newEnemyPos;
                }
                int fallenTimer = player.FallenTimer;
                int tackleTimer = player.TackleTimer;
                Vector newPlayerPos = player.Position;
                Vector newPlayerVel = player.Velocity;
                for (int j = 1; j <= i; j++)
                {
                    fallenTimer--;
                    tackleTimer--;
                    newPlayerPos = SimulateMove(newPlayerPos, newPlayerVel, newEnemyPos, out newPlayerVel);
                    if (newPlayerPos.GetDistanceTo(newEnemyPos) < Constants.PlayerMaxTackleDistance && fallenTimer <= 0 && tackleTimer <= 0)
                        return i;
                }
            }
            return int.MaxValue;
        }

        private List<Player> ProcessTacklers(Team myTeam, Team enemyTeam, Ball ball)
        {
            List<Player> tacklers = new List<Player>(6);
            if (ball.PickUpTimer <= 0 && passGiveTimer <= 0) return tacklers;
            List<Player> closeEnemies =
                enemyTeam.Players
                    .Where(
                        e =>
                        e.FallenTimer <= ball.PickUpTimer &&
                        e.GetDistanceTo(ball) <= Constants.BallMaxPickUpDistance).ToList();
            List<Player> myPlayers = myTeam.Players
                .Where(p => p.FallenTimer < ball.PickUpTimer && p.TackleTimer < ball.PickUpTimer).ToList();
            foreach (Player closeEnemy in closeEnemies)
            {
                List<Pair<Player, int>> playersSteps = new List<Pair<Player, int>>(6);
                Vector target = Vector.Zero;
                playersSteps
                    .AddRange(myPlayers.Select(player => new Pair<Player, int>(player, StepsToTackle(ball, closeEnemy, player, out target)))
                    .Where(playerSteps => playerSteps.Second < int.MaxValue));
                if (playersSteps.Count <= 0) continue;
				if (playersSteps.Count > 1)
					playersSteps.Sort(PlayerComparer.ComparePlayerStepsBySteps);
            	tacklers.Add(playersSteps[0].First);
                if (playersSteps[0].Second == 0)
                    playersSteps[0].First.ActionTackle(closeEnemy);
                else
                    playersSteps[0].First.ActionGo(target == Vector.Zero ? closeEnemy.Position : target);
                myPlayers.Remove(playersSteps[0].First);
            }
            
            return tacklers;
        }

	    private bool ballWillBeMine;
	    private bool ballIsMine;

		private void DefaultTactic(Team myTeam, Team enemyTeam, Ball ball, MatchInfo matchInfo)
		{
		    Catcher catcher;
            Catcher enemyCatcher;
            ballWillBeMine = ProcessCatcher(myTeam, enemyTeam, ball, out catcher, out enemyCatcher);
		    ballIsMine = ball.Owner == null ? ballWillBeMine : ball.Owner.Team == myTeam;
		    Player ballOwner = ball.Owner ?? (ballWillBeMine ? catcher.Player : enemyCatcher.Player);

            List<Player> tacklers = ProcessTacklers(myTeam, enemyTeam, ball);

            MoveToDefencePositions(myTeam, enemyTeam, ball, catcher, enemyCatcher, tacklers);

			foreach (Player player in myTeam.Players.Where(player => player.FallenTimer <= 0))
            {
                if (player.CanPickUpBall(ball) && (!ballWillBeMine || passGiveTimer == 0 || player.PlayerType != passGiverType))
                {
                    player.ActionPickUpBall();
                    continue;
                }
                if (catcher.Player == player || tacklers.Contains(player))
                    continue;

                if ((player.PlayerType == PlayerType.Keeper) && (ball.Owner != player))
                {
                    KeeperDefend(ball, player, enemyCatcher);
                    continue;
                }
                
                if (ballIsMine)
                {
                    if (ball.Owner != null)
                        FillPlayersEx(ball.Owner == null ? catcher.CatchPos : ball.Position, ballOwner, myTeam.Players, enemyTeam.Players);

                    if (ball.Owner == player)
                        PlayerWithBallDecision(player, myTeam, enemyTeam, matchInfo);
                    else
                        player.ActionGo(FindPositionForOpen(player, ball.Owner == null ? catcher.CatchPos : ball.Owner.Position));
                }
                else
                {
                	passGiveTimer = 0;

                    if (player.CanTackle(ball.Owner ?? enemyCatcher.Player))
                        player.ActionTackle(ball.Owner ?? enemyCatcher.Player);
                }
            }
#if LOG
			if (ball.Owner != null)
				lastOwner = ball.Owner.Team;
			else if (matchInfo.EnemyTeamScored || matchInfo.MyTeamScored)
				lastOwner = null;
#endif
		}

    	public void Action(Team myTeam, Team enemyTeam, Ball ball, MatchInfo matchInfo)
		{
			InitAction();
#if LOG
			stepNumber = matchInfo.CurrentTimeStep;
			if (isRedTeam)
			{
				if (logBallVel)
				{
					logBallVel = false;
					Logger.Log("ballVel {0:0.00} {1:0.00}", ball.Velocity.X, ball.Velocity.Y);
				}
				if (lastOwner == myTeam && ball.Owner != null && ball.Owner.Team == enemyTeam)
					Logger.Log("pass intercepted by {2} at {0:0.00} {1:0.00}", ball.Owner.Position.X, ball.Owner.Position.Y,
					           Logger.TypeToStr(ball.Owner.PlayerType));
			}
#endif
			
			if (DetectInLeadTactics(enemyTeam))
				AntiInLeadTactic(myTeam, enemyTeam, ball);
			else
				DefaultTactic(myTeam, enemyTeam, ball, matchInfo);
		}

    	private void InitAction()
    	{
    		playersExFilled = false;
    		reflectPlayersExFilled = false;
			MyPlayersEx.Clear();
			EnemyPlayersEx.Clear();
			myReflectPlayersEx.Clear();
			myPlayersExPassWithoutOneEnemy.Clear();
    	    myBallOwnerExShootWithoutOneEnemy = null;

			if (passGiveTimer > 0)
				passGiveTimer--;
    	}
    }
}
